package services.third;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;

import play.Logger;
import play.Play;
import play.libs.F.Function;
import play.libs.F.Promise;
import play.libs.WS;
import play.mvc.Http.Response;
import scala.annotation.target.param;
import services.core.CoreService;
import controllers.facebook.FacebookException;

public class FacebookService extends CoreService {

    private final static String FB_GRAPH_URL = "https://graph.facebook.com/";
    private static String appId = Play.application().configuration().getString("vvins.fb.appId");
    private static String appSecret = Play.application().configuration().getString("vvins.fb.appSecret");

    private static Map<String, String> parseStr(String str) {
        String[] pairs = str.split("&");
        Map<String, String> map = new HashMap<String, String>();
        for (String pair : pairs) {
            String[] kv = pair.split("=");
            map.put(kv[0], kv[1]);
        }
        return map;
    }

    
    /**
     * Performs an authorized request to the Graph API.
     *
     * @param   path - the URL path
     * @param   method - the HTTP method (optional, default "GET")
     * @param   params - the parameters for the query (optional)
     * @return  the HTTP response
     */
    public static Promise<WS.Response> oauthRequest(String accessToken, String path, String method, Map<String, String> params) throws FacebookException {
        if (params == null) {
            params = new HashMap<String, String>();
        }
        if (accessToken != null && !params.containsKey("access_token")) {
            params.put("access_token",accessToken);
        }
        if(!params.containsKey("access_token")){
            throw new FacebookException("No valid access token");
        }
        return makeRequest(path, method, params);
    }
    

    /**
     * Returns the user access token associated with this application.
     *
     * @return  the user access token
     *          or null otherwise
     */
    public static String getAccessToken(String code) throws FacebookException {
        String accessToken = null;
            if (code != null) {
            	Map<String, String> parts = WS.url(FB_GRAPH_URL + "oauth/access_token?")
            			.setQueryParameter("client_id",  getAppId())
            			.setQueryParameter("client_secret",  getAppSecret())
            			.setQueryParameter("redirect_uri",  "")
            			.setQueryParameter("code",  code)
            			.setQueryParameter("client_id=",  getAppId())
                        .get().map(
                        		new Function<WS.Response, Map<String,String>>() {
                    		        public Map<String,String> apply(WS.Response response) {
                    		        	if(response.getBody() != null && !response.getBody().isEmpty()){
                    		        		return parseStr(response.getBody());
                    		        	}
                    		        	return null;
                    		        }
                    		    }        		
                ).get();
                if (parts != null && parts.containsKey("access_token")) {
                    accessToken = parts.get("access_token");
                } else {
                	throw new FacebookException("No valid access token");
                }
            }
        Logger.debug("Access token: " + accessToken);
        return accessToken;
    }
    
    /**
     * Executes an API call to the Graph API.
     *
     * @param   path - the URL path, e.g. "me/friends"
     * @param   method - the HTTP method (optional, default "GET")
     * @param   params - the parameters for the query (optional)
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode api(String accesstoken, String path, String method, Map<String, String> params) throws FacebookException {
        WS.Response res = oauthRequest(accesstoken, path, method, params).get();
        if (res == null) {
        	throw new FacebookException("Module FbGraph got an unexpected response from facebook");
        }
        if (res.getStatus() != 200) {
            throw new FacebookException("Response" + Integer.toString(res.getStatus()) + res.getBody());
        }
        JsonNode json = res.asJson();
        if (json.isObject()) {
            return json;
        } else if (json.get("error") != null) {
                throw new FacebookException(json.get("error").asText());
        }
        return json;
    }
    
    /**
     * Executes an API call to the Graph API.
     *
     * @param   path - the URL path, e.g. "me/friends"
     * @param   method - the HTTP method (optional, default "GET")
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode api(String accesstoken, String path, String method) throws FacebookException {
        return api(accesstoken, path, method, new HashMap<String, String>());
    }

    /**
     * Executes an API call to the Graph API.
     *
     * @param   path - the URL path, e.g. "me/friends"
     * @param   params - the parameters for the query (optional)
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode api(String accesstoken, String path, Map<String, String> params) throws FacebookException {
        return api(accesstoken, path, "GET", params);
    }

    /**
     * Executes an API call to the Graph API.
     *
     * @param   path - the URL path, e.g. "me/friends"
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode api(String accesstoken, String path) throws FacebookException {
        return api(accesstoken, path, "GET", new HashMap<String, String>());
    }
    
    /**
     * Fetches a single object to the Graph API.
     *
     * @param   objId - the ID of the object, e.g. "me"
     * @param   params - the parameters for the query (optional)
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode getObject(String accesstoken, String objId, Map<String, String> params) throws FacebookException {
      return FacebookService.api(accesstoken, objId, params);
    }
    
    /**
     * Fetches a single object to the Graph API.
     *
     * @param   objId - the ID of the object, e.g. "me"
     * @param   clazz - the object type
     * @param   params - the parameters for the query (optional)
     * @return  the response object
     * @throws  FacebookException
     */
    public static <T> T getObject(String accesstoken, String objId, Class<T> clazz, Map<String, String> params) throws FacebookException {
        try {
			return mapper.readValue(FacebookService.getObject(accesstoken, objId, params), clazz);
		} catch (JsonParseException e) {
			throw new FacebookException(e);
		} catch (JsonMappingException e) {
			throw new FacebookException(e);
		} catch (IOException e) {
			throw new FacebookException(e);
		}
    }
    
    /**
     * Fetches a Graph API connection.
     *
     * @param   conId - the ID/CONNECTION_TYPE string, e.g. "me/friends"
     * @param   params - the parameters for the query (optional)
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode getConnection(String accesstoken, String conId, Map<String, String> params) throws FacebookException {
        return api(accesstoken ,conId, params).get("data");
    }
    
    /**
     * Fetches a Graph API connection.
     *
     * @param   conId - the ID/CONNECTION_TYPE string, e.g. "me/friends"
     * @param   clazz - the object type
     * @param   params - the parameters for the query (optional)
     * @return  the response object
     * @throws  FacebookException
     */
    public static <T> List<T> getConnection(String accesstoken, String conId, Class<T> clazz, Map<String, String> params)
            throws FacebookException {
        try {
			return (List<T>) mapper.readValue(getConnection(accesstoken, conId, params), clazz);
		} catch (JsonParseException e) {
			throw new FacebookException(e);
		} catch (JsonMappingException e) {
			throw new FacebookException(e);
		} catch (IOException e) {
			throw new FacebookException(e);
		}
    }
    

    /**
     * Fetches a Graph API connection.
     *
     * @param   conId - the ID/CONNECTION_TYPE string, e.g. "me/friends"
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode getConnection(String accesstoken, String conId) throws FacebookException {
        return getConnection(accesstoken, conId, new HashMap<String, String>());
    }
    
    /**
     * Fetches a Graph API connection.
     *
     * @param   conId - the ID/CONNECTION_TYPE string, e.g. "me/friends"
     * @param   clazz - the object type
     * @return  the response object
     * @throws  FacebookException
     */
    public static <T> List<T> getConnection(String accesstoken, String conId, Class<T> clazz) throws FacebookException {
        return getConnection(accesstoken, conId, clazz, new HashMap<String, String>());
    }

    /**
     * Fetches a single Graph API object.
     *
     * @param   objId - the ID of the object, e.g. "me"
     * @param   clazz - the object type
     * @return  the response object
     * @throws  FacebookException
     */
    public static <T> T getObject(String accesstoken, String objId, Class<T> clazz) throws FacebookException {
        return getObject(accesstoken, objId, clazz, new HashMap<String, String>());
    }

    
    /**
     * Performs a Graph API publish operation.
     *
     * @param   path - the URL path
     * @param   params - the parameters of the post
     * @return  the published Facebook graph object
     * @throws  FacebookException
     */
    public static JsonNode publish(String accesstoken, String path, Map<String, String> params) throws FacebookException {
        return FacebookService.api(accesstoken, path, "POST", params);
    }
    
    /**
     * Performs a Graph API publish operation.
     *
     * @param   path - the URL path
     * @param   params - the parameters of the post
     * @param   clazz - the object type
     * @return  the published Facebook graph object
     * @throws  FacebookException
     */
    public static <T> T publish(String accesstoken, String path, Class<T> clazz, Map<String, String> params) throws FacebookException {
        try {
			return mapper.readValue(publish(accesstoken, path, params).toString(), clazz);
		} catch (JsonParseException e) {
			throw new FacebookException(e);
		} catch (JsonMappingException e) {
			throw new FacebookException(e);
		} catch (IOException e) {
			throw new FacebookException(e);
		}
    }
    
    
    /**
     * Fetches a single Graph API object.
     *
     * @param   objId - the ID of the object, e.g. "me"
     * @return  the response object
     * @throws  FacebookException
     */
    public static JsonNode getObject(String accesstoken, String objId) throws FacebookException {
        return getObject(accesstoken, objId, new HashMap<String, String>());
    }
    
    /**
     * Executes a GET or POST request to the Graph API.
     *
     * @param   path - the URL path
     * @param   method - the HTTP method (optional, default "GET")
     * @param   params - the parameters for the query (optional)
     * @return  the HTTP response
     */
    private static Promise<WS.Response> makeRequest(String path, String method, Map<String, String> params) {
        StringBuilder url = new StringBuilder();
        url.append(FB_GRAPH_URL);
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        url.append(path);
        WS.url(url.toString()).setHeader(CONTENT_TYPE, "multipart/form-data");
        if (method != null && method.toUpperCase().equals("POST")) {
        	 StringBuilder queryStr = new StringBuilder();
             for (Map.Entry<String, String> param : params.entrySet()) {
                 if (queryStr.length() > 0) {
                     queryStr.append("&");
                 }
                 queryStr.append(param.getKey());
                 queryStr.append("=");
                 queryStr.append(param.getValue());
             }
            Logger.info("Making a POST request to URL "+ url.toString() +" with body set to " + queryStr.toString());
            return WS.url(url.toString()).setHeader(CONTENT_TYPE, "multipart/form-data").setHeader(Response.CONTENT_TYPE, "multipart/form-data").post(queryStr.toString());
        } else {
            Logger.info("Making a GET request to URL " + url.toString());
            WS.WSRequestHolder req = WS.url(url.toString()).setHeader(CONTENT_TYPE, "multipart/form-data");
            for (Map.Entry<String, String> param : params.entrySet()) {
            	req.setQueryParameter(param.getKey(), param.getValue());
            }
           return req.get();
        }
    }
    
 
    
    


    
    /**
     * Returns the Id associated to this application.
     *
     * @return  the application Id
     */
    public static String getAppId() {
        return appId;
    }

    /**
     * Returns the Secret associated to this application.
     *
     * @return  the application Secret
     */
    public static String getAppSecret() {
        return appSecret;
    }
    
}
